# 收入贡献逐步推导

本章所有展示金额单位均为元。代码内部单位为分。目标是解释下降的 99,000 元怎样分给四项因素，以及为什么不能直接把各种百分比加起来。

## 先对账 再分解

| 指标 | 2026 年 7 月 | 2026 年 8 月 | 变化 |
| --- | --- | --- | --- |
| 支付订单数 | 1,000 | 800 | −200 |
| 实付金额 | 250,000 | 159,000 | −91,000 |
| 成功退款 | 10,000 | 18,000 | +8,000 |
| 净收入 | 240,000 | 141,000 | −99,000 |
| 总体实付客单价 | 250.00 | 198.75 | −51.25 |

净收入降幅 = −99,000 / 240,000 = −41.25%。客单价分母是支付订单数，分子是实付金额，未扣退款；退款单独处理。

| 渠道 | 七月订单 | 八月订单 | 七月占比 | 八月占比 | 七月客单价 | 八月客单价 |
| --- | --- | --- | --- | --- | --- | --- |
| 自营 | 600 | 300 | 60% | 37.5% | 300 | 280 |
| 搜索广告 | 300 | 300 | 30% | 37.5% | 200 | 190 |
| 直播 | 100 | 200 | 10% | 25% | 100 | 90 |

总体客单价为什么下降？一方面每个渠道内部客单价都下降；另一方面低客单价直播渠道的订单份额变大，高客单价自营份额变小。第二种变化即使所有渠道价格完全不变，也会拉低总体客单价。

## 用恒等式把问题写清楚

定义 N 为总订单数，s 为各渠道订单占比组成的向量，a 为各渠道客单价组成的向量，R 为当月成功退款。实付 G 与净收入 Y 满足：

```text
G(N, s, a) = N × Σ(s_c × a_c)
Y = G(N, s, a) − R
```

Σ 表示对所有渠道相加。七月加权客单价是 0.6×300 + 0.3×200 + 0.1×100 = 250，乘以 1000 单得到 250,000。八月加权客单价是 0.375×280 + 0.375×190 + 0.25×90 = 198.75，乘以 800 得到 159,000。

“渠道结构”在这里专指订单占比，不是收入占比。选择收入占比会改变公式，不能换了分母还照用当前贡献算法。

## 为什么替换顺序会影响分账

暂时只看订单量与客单价。用七月客单价衡量少掉的 200 单，得到 −200×250 = −50,000；用八月客单价衡量同样的 200 单，得到 −200×198.75 = −39,750。两者都在回答一个反事实问题，但参考环境不同。

差异来自交互项：订单少了，同时价格和结构也变了。“先改谁”决定交互影响归谁。直接选一种顺序可能可以作为业务约定，但必须承认约定带来的分配差异。本项目枚举所有顺序并平均，减少对单一顺序的依赖。

## 手算一种顺序

取 N → s → a。从七月状态出发，只替换订单量，其他仍是七月：G = 800×250 = 200,000，所以 N 的本次边际贡献为 −50,000。

再替换渠道结构，保持七月各渠道客单价：加权价变成 0.375×300 + 0.375×200 + 0.25×100 = 212.5；G = 800×212.5 = 170,000，所以 s 的本次边际贡献为 −30,000。

最后替换渠道内客单价，G 变成 159,000，所以 a 的本次边际贡献为 −11,000。这条路径合计 −91,000，恰好等于实付变化。

中间的 200,000 和 170,000 是把不同月份因素拼接得到的计算情景，并不是数据库里真实存在的某个月收入。

## 六种顺序与最终贡献

下表每列都按因素归位，列的排列不代表替换顺序。

| 替换顺序 | N 的边际贡献 | s 的边际贡献 | a 的边际贡献 | 合计 |
| --- | --- | --- | --- | --- |
| N → s → a | −50,000 | −30,000 | −11,000 | −91,000 |
| N → a → s | −50,000 | −28,200 | −12,800 | −91,000 |
| s → N → a | −42,500 | −37,500 | −11,000 | −91,000 |
| s → a → N | −39,750 | −37,500 | −13,750 | −91,000 |
| a → N → s | −46,800 | −28,200 | −16,000 | −91,000 |
| a → s → N | −39,750 | −35,250 | −16,000 | −91,000 |
| 六行平均 | **−44,800** | **−32,775** | **−13,425** | **−91,000** |

这就是当前三个因素的 Shapley 算术分解：每个因素取它在全部排列中的平均边际差。这里没有训练模型，也没有使用解释机器学习预测的 SHAP 包。它是对收入恒等式做精确计算。

退款项在净收入中直接相减，与上述乘法因素没有交互，因此贡献直接为 R_七月 − R_八月 = 10,000 − 18,000 = −8,000。即使把退款加入四因素全部排列，其独立加法结构也会使它的边际贡献恒定。

最终对账：−44,800 −32,775 −13,425 −8,000 = −99,000。每条替换路径的边际差会逐步抵消中间值，只留下期末减期初；对所有路径求平均仍保留总变化。这是贡献能精确加总的原因。

## 对照代码看实现

`decompose` 先把六行分为 previous 和 current 两组，再计算各期总订单、实付、退款、净收入和客单价。接着构造 `values[0]` 与 `values[1]`，每组都包含 N、s 向量和 a 向量。

`permutations(range(3))` 产生六个顺序。每次从旧状态开始，替换一个因素，计算 `after-before`，将其六分之一累加到该因素贡献。每步的精确边际值也保存到 `permutations`，所以读者可以按证据逐步复算。

```python
for order in permutations(range(3)):
    state = 旧月份因素
    before = gross(state)
    for factor in order:
        state[factor] = 新月份对应因素
        after = gross(state)
        contribution[factor] += (after - before) / 6
        before = after
```

上面是解释用伪代码，实际实现使用 Fraction 保存比例和中间金额。用分存金额可以避免原始货币的小数误差，但占比和平均客单价仍可能是无限循环小数，所以还需要分数运算。最终输出既保存便于展示的浮点值，也保存 `exact_cents` 精确字符串。先完成精确相加断言，再转成展示格式。

## 边界怎样处理

某个渠道单月零订单时，它的客单价在数学上没有定义。代码借用另一月该渠道客单价；两月均为零则取零，并记录填补说明。这个约定会影响结构与价格的分账，是必须披露的假设，不能说它是唯一正确的答案。

若整个地区某一整月无支付订单，连渠道占比也无法定义，程序直接拒绝这类分解。若基期净收入为零，增长率返回不可定义；若净变化为零，各因素仍可能相互抵消，贡献占变化比例也不可定义。

贡献比例是“某项带符号贡献 / 总净变化”。当有的因素改善、有的因素恶化时，它可能为负或超过 100%，不能把它解释成概率。

## 不能从贡献直接跳到原因

“订单量贡献 −44,800 元”表示按当前恒等式和分配规则，订单数量变化分到了多少收入差额。它不证明广告投放下降、竞品促销或流量流失。

想解释为什么少了订单，需要访问量、转化率和曝光数据；想解释客单价变化，需要商品组合、促销和折扣；想解释退款，需要退款原因与履约证据。当前报告把这些列为待验证假设。

总体客单价的下降已经拆进渠道结构与渠道内客单价，不能再加一项“总体客单价贡献”，否则重复分账。这是讲解时最容易被追问、也最应该说明白的一点。
